op-geth/swarm/pss/pss.go

858 lines
25 KiB
Go
Raw Normal View History

2018-06-20 12:06:27 +00:00
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package pss
import (
"bytes"
"context"
2018-06-20 12:06:27 +00:00
"crypto/ecdsa"
"crypto/rand"
"errors"
"fmt"
"hash"
2018-06-20 12:06:27 +00:00
"sync"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/p2p"
all: new p2p node representation (#17643) Package p2p/enode provides a generalized representation of p2p nodes which can contain arbitrary information in key/value pairs. It is also the new home for the node database. The "v4" identity scheme is also moved here from p2p/enr to remove the dependency on Ethereum crypto from that package. Record signature handling is changed significantly. The identity scheme registry is removed and acceptable schemes must be passed to any method that needs identity. This means records must now be validated explicitly after decoding. The enode API is designed to make signature handling easy and safe: most APIs around the codebase work with enode.Node, which is a wrapper around a valid record. Going from enr.Record to enode.Node requires a valid signature. * p2p/discover: port to p2p/enode This ports the discovery code to the new node representation in p2p/enode. The wire protocol is unchanged, this can be considered a refactoring change. The Kademlia table can now deal with nodes using an arbitrary identity scheme. This requires a few incompatible API changes: - Table.Lookup is not available anymore. It used to take a public key as argument because v4 protocol requires one. Its replacement is LookupRandom. - Table.Resolve takes *enode.Node instead of NodeID. This is also for v4 protocol compatibility because nodes cannot be looked up by ID alone. - Types Node and NodeID are gone. Further commits in the series will be fixes all over the the codebase to deal with those removals. * p2p: port to p2p/enode and discovery changes This adapts package p2p to the changes in p2p/discover. All uses of discover.Node and discover.NodeID are replaced by their equivalents from p2p/enode. New API is added to retrieve the enode.Node instance of a peer. The behavior of Server.Self with discovery disabled is improved. It now tries much harder to report a working IP address, falling back to 127.0.0.1 if no suitable address can be determined through other means. These changes were needed for tests of other packages later in the series. * p2p/simulations, p2p/testing: port to p2p/enode No surprises here, mostly replacements of discover.Node, discover.NodeID with their new equivalents. The 'interesting' API changes are: - testing.ProtocolSession tracks complete nodes, not just their IDs. - adapters.NodeConfig has a new method to create a complete node. These changes were needed to make swarm tests work. Note that the NodeID change makes the code incompatible with old simulation snapshots. * whisper/whisperv5, whisper/whisperv6: port to p2p/enode This port was easy because whisper uses []byte for node IDs and URL strings in the API. * eth: port to p2p/enode Again, easy to port because eth uses strings for node IDs and doesn't care about node information in any way. * les: port to p2p/enode Apart from replacing discover.NodeID with enode.ID, most changes are in the server pool code. It now deals with complete nodes instead of (Pubkey, IP, Port) triples. The database format is unchanged for now, but we should probably change it to use the node database later. * node: port to p2p/enode This change simply replaces discover.Node and discover.NodeID with their new equivalents. * swarm/network: port to p2p/enode Swarm has its own node address representation, BzzAddr, containing both an overlay address (the hash of a secp256k1 public key) and an underlay address (enode:// URL). There are no changes to the BzzAddr format in this commit, but certain operations such as creating a BzzAddr from a node ID are now impossible because node IDs aren't public keys anymore. Most swarm-related changes in the series remove uses of NewAddrFromNodeID, replacing it with NewAddr which takes a complete node as argument. ToOverlayAddr is removed because we can just use the node ID directly.
2018-09-24 22:59:00 +00:00
"github.com/ethereum/go-ethereum/p2p/enode"
2018-06-20 12:06:27 +00:00
"github.com/ethereum/go-ethereum/p2p/protocols"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/swarm/log"
"github.com/ethereum/go-ethereum/swarm/network"
"github.com/ethereum/go-ethereum/swarm/pot"
"github.com/ethereum/go-ethereum/swarm/storage"
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
"golang.org/x/crypto/sha3"
2018-06-20 12:06:27 +00:00
)
const (
defaultPaddingByteSize = 16
DefaultMsgTTL = time.Second * 120
2018-06-20 12:06:27 +00:00
defaultDigestCacheTTL = time.Second * 10
defaultSymKeyCacheCapacity = 512
digestLength = 32 // byte length of digest used for pss cache (currently same as swarm chunk hash)
defaultWhisperWorkTime = 3
defaultWhisperPoW = 0.0000000001
defaultMaxMsgSize = 1024 * 1024
defaultCleanInterval = time.Second * 60 * 10
defaultOutboxCapacity = 100000
pssProtocolName = "pss"
pssVersion = 2
hasherCount = 8
)
var (
addressLength = len(pot.Address{})
)
// cache is used for preventing backwards routing
// will also be instrumental in flood guard mechanism
// and mailbox implementation
type pssCacheEntry struct {
expiresAt time.Time
}
// abstraction to enable access to p2p.protocols.Peer.Send
type senderPeer interface {
Info() *p2p.PeerInfo
all: new p2p node representation (#17643) Package p2p/enode provides a generalized representation of p2p nodes which can contain arbitrary information in key/value pairs. It is also the new home for the node database. The "v4" identity scheme is also moved here from p2p/enr to remove the dependency on Ethereum crypto from that package. Record signature handling is changed significantly. The identity scheme registry is removed and acceptable schemes must be passed to any method that needs identity. This means records must now be validated explicitly after decoding. The enode API is designed to make signature handling easy and safe: most APIs around the codebase work with enode.Node, which is a wrapper around a valid record. Going from enr.Record to enode.Node requires a valid signature. * p2p/discover: port to p2p/enode This ports the discovery code to the new node representation in p2p/enode. The wire protocol is unchanged, this can be considered a refactoring change. The Kademlia table can now deal with nodes using an arbitrary identity scheme. This requires a few incompatible API changes: - Table.Lookup is not available anymore. It used to take a public key as argument because v4 protocol requires one. Its replacement is LookupRandom. - Table.Resolve takes *enode.Node instead of NodeID. This is also for v4 protocol compatibility because nodes cannot be looked up by ID alone. - Types Node and NodeID are gone. Further commits in the series will be fixes all over the the codebase to deal with those removals. * p2p: port to p2p/enode and discovery changes This adapts package p2p to the changes in p2p/discover. All uses of discover.Node and discover.NodeID are replaced by their equivalents from p2p/enode. New API is added to retrieve the enode.Node instance of a peer. The behavior of Server.Self with discovery disabled is improved. It now tries much harder to report a working IP address, falling back to 127.0.0.1 if no suitable address can be determined through other means. These changes were needed for tests of other packages later in the series. * p2p/simulations, p2p/testing: port to p2p/enode No surprises here, mostly replacements of discover.Node, discover.NodeID with their new equivalents. The 'interesting' API changes are: - testing.ProtocolSession tracks complete nodes, not just their IDs. - adapters.NodeConfig has a new method to create a complete node. These changes were needed to make swarm tests work. Note that the NodeID change makes the code incompatible with old simulation snapshots. * whisper/whisperv5, whisper/whisperv6: port to p2p/enode This port was easy because whisper uses []byte for node IDs and URL strings in the API. * eth: port to p2p/enode Again, easy to port because eth uses strings for node IDs and doesn't care about node information in any way. * les: port to p2p/enode Apart from replacing discover.NodeID with enode.ID, most changes are in the server pool code. It now deals with complete nodes instead of (Pubkey, IP, Port) triples. The database format is unchanged for now, but we should probably change it to use the node database later. * node: port to p2p/enode This change simply replaces discover.Node and discover.NodeID with their new equivalents. * swarm/network: port to p2p/enode Swarm has its own node address representation, BzzAddr, containing both an overlay address (the hash of a secp256k1 public key) and an underlay address (enode:// URL). There are no changes to the BzzAddr format in this commit, but certain operations such as creating a BzzAddr from a node ID are now impossible because node IDs aren't public keys anymore. Most swarm-related changes in the series remove uses of NewAddrFromNodeID, replacing it with NewAddr which takes a complete node as argument. ToOverlayAddr is removed because we can just use the node ID directly.
2018-09-24 22:59:00 +00:00
ID() enode.ID
2018-06-20 12:06:27 +00:00
Address() []byte
Send(context.Context, interface{}) error
2018-06-20 12:06:27 +00:00
}
// per-key peer related information
// member `protected` prevents garbage collection of the instance
type pssPeer struct {
lastSeen time.Time
address PssAddress
2018-06-20 12:06:27 +00:00
protected bool
}
// Pss configuration parameters
type PssParams struct {
MsgTTL time.Duration
CacheTTL time.Duration
privateKey *ecdsa.PrivateKey
SymKeyCacheCapacity int
AllowRaw bool // If true, enables sending and receiving messages without builtin pss encryption
}
// Sane defaults for Pss
func NewPssParams() *PssParams {
return &PssParams{
MsgTTL: DefaultMsgTTL,
2018-06-20 12:06:27 +00:00
CacheTTL: defaultDigestCacheTTL,
SymKeyCacheCapacity: defaultSymKeyCacheCapacity,
}
}
func (params *PssParams) WithPrivateKey(privatekey *ecdsa.PrivateKey) *PssParams {
params.privateKey = privatekey
return params
}
// Toplevel pss object, takes care of message sending, receiving, decryption and encryption, message handler dispatchers and message forwarding.
//
// Implements node.Service
type Pss struct {
*network.Kademlia // we can get the Kademlia address from this
*KeyStore
privateKey *ecdsa.PrivateKey // pss can have it's own independent key
auxAPIs []rpc.API // builtins (handshake, test) can add APIs
2018-06-20 12:06:27 +00:00
// sending and forwarding
fwdPool map[string]*protocols.Peer // keep track of all peers sitting on the pssmsg routing layer
fwdPoolMu sync.RWMutex
fwdCache map[pssDigest]pssCacheEntry // checksum of unique fields from pssmsg mapped to expiry, cache to determine whether to drop msg
fwdCacheMu sync.RWMutex
cacheTTL time.Duration // how long to keep messages in fwdCache (not implemented)
msgTTL time.Duration
paddingByteSize int
capstring string
outbox chan *PssMsg
// message handling
handlers map[Topic]map[*handler]bool // topic and version based pss payload handlers. See pss.Handle()
handlersMu sync.RWMutex
hashPool sync.Pool
topicHandlerCaps map[Topic]*handlerCaps // caches capabilities of each topic's handlers
topicHandlerCapsMu sync.RWMutex
2018-06-20 12:06:27 +00:00
// process
quitC chan struct{}
}
func (p *Pss) String() string {
return fmt.Sprintf("pss: addr %x, pubkey %v", p.BaseAddr(), common.ToHex(crypto.FromECDSAPub(&p.privateKey.PublicKey)))
}
// Creates a new Pss instance.
//
// In addition to params, it takes a swarm network Kademlia
2018-06-20 12:06:27 +00:00
// and a FileStore storage for message cache storage.
func NewPss(k *network.Kademlia, params *PssParams) (*Pss, error) {
2018-06-20 12:06:27 +00:00
if params.privateKey == nil {
return nil, errors.New("missing private key for pss")
}
cap := p2p.Cap{
Name: pssProtocolName,
Version: pssVersion,
}
ps := &Pss{
Kademlia: k,
KeyStore: loadKeyStore(),
2018-06-20 12:06:27 +00:00
privateKey: params.privateKey,
quitC: make(chan struct{}),
fwdPool: make(map[string]*protocols.Peer),
fwdCache: make(map[pssDigest]pssCacheEntry),
cacheTTL: params.CacheTTL,
msgTTL: params.MsgTTL,
paddingByteSize: defaultPaddingByteSize,
capstring: cap.String(),
outbox: make(chan *PssMsg, defaultOutboxCapacity),
handlers: make(map[Topic]map[*handler]bool),
topicHandlerCaps: make(map[Topic]*handlerCaps),
2018-06-20 12:06:27 +00:00
hashPool: sync.Pool{
New: func() interface{} {
return sha3.NewLegacyKeccak256()
2018-06-20 12:06:27 +00:00
},
},
}
for i := 0; i < hasherCount; i++ {
hashfunc := storage.MakeHashFunc(storage.DefaultHash)()
ps.hashPool.Put(hashfunc)
}
return ps, nil
}
/////////////////////////////////////////////////////////////////////
// SECTION: node.Service interface
/////////////////////////////////////////////////////////////////////
func (p *Pss) Start(srv *p2p.Server) error {
go func() {
ticker := time.NewTicker(defaultCleanInterval)
cacheTicker := time.NewTicker(p.cacheTTL)
defer ticker.Stop()
defer cacheTicker.Stop()
for {
select {
case <-cacheTicker.C:
p.cleanFwdCache()
case <-ticker.C:
p.cleanKeys()
case <-p.quitC:
return
}
}
}()
go func() {
for {
select {
case msg := <-p.outbox:
err := p.forward(msg)
if err != nil {
log.Error(err.Error())
metrics.GetOrRegisterCounter("pss.forward.err", nil).Inc(1)
}
case <-p.quitC:
return
}
}
}()
log.Info("Started Pss")
log.Info("Loaded EC keys", "pubkey", common.ToHex(crypto.FromECDSAPub(p.PublicKey())), "secp256", common.ToHex(crypto.CompressPubkey(p.PublicKey())))
2018-06-20 12:06:27 +00:00
return nil
}
func (p *Pss) Stop() error {
log.Info("Pss shutting down")
2018-06-20 12:06:27 +00:00
close(p.quitC)
return nil
}
var pssSpec = &protocols.Spec{
Name: pssProtocolName,
Version: pssVersion,
MaxMsgSize: defaultMaxMsgSize,
Messages: []interface{}{
PssMsg{},
},
}
func (p *Pss) Protocols() []p2p.Protocol {
return []p2p.Protocol{
{
Name: pssSpec.Name,
Version: pssSpec.Version,
Length: pssSpec.Length(),
Run: p.Run,
},
}
}
func (p *Pss) Run(peer *p2p.Peer, rw p2p.MsgReadWriter) error {
pp := protocols.NewPeer(peer, rw, pssSpec)
p.fwdPoolMu.Lock()
p.fwdPool[peer.Info().ID] = pp
p.fwdPoolMu.Unlock()
return pp.Run(p.handlePssMsg)
}
func (p *Pss) APIs() []rpc.API {
apis := []rpc.API{
{
Namespace: "pss",
Version: "1.0",
Service: NewAPI(p),
Public: true,
},
}
apis = append(apis, p.auxAPIs...)
return apis
}
// add API methods to the pss API
// must be run before node is started
func (p *Pss) addAPI(api rpc.API) {
p.auxAPIs = append(p.auxAPIs, api)
}
// Returns the swarm Kademlia address of the pss node
2018-06-20 12:06:27 +00:00
func (p *Pss) BaseAddr() []byte {
return p.Kademlia.BaseAddr()
2018-06-20 12:06:27 +00:00
}
// Returns the pss node's public key
func (p *Pss) PublicKey() *ecdsa.PublicKey {
return &p.privateKey.PublicKey
}
/////////////////////////////////////////////////////////////////////
// SECTION: Message handling
/////////////////////////////////////////////////////////////////////
func (p *Pss) getTopicHandlerCaps(topic Topic) (hc *handlerCaps, found bool) {
p.topicHandlerCapsMu.RLock()
defer p.topicHandlerCapsMu.RUnlock()
hc, found = p.topicHandlerCaps[topic]
return
}
func (p *Pss) setTopicHandlerCaps(topic Topic, hc *handlerCaps) {
p.topicHandlerCapsMu.Lock()
defer p.topicHandlerCapsMu.Unlock()
p.topicHandlerCaps[topic] = hc
}
2018-06-20 12:06:27 +00:00
// Links a handler function to a Topic
//
// All incoming messages with an envelope Topic matching the
// topic specified will be passed to the given Handler function.
//
// There may be an arbitrary number of handler functions per topic.
//
// Returns a deregister function which needs to be called to
// deregister the handler,
func (p *Pss) Register(topic *Topic, hndlr *handler) func() {
2018-06-20 12:06:27 +00:00
p.handlersMu.Lock()
defer p.handlersMu.Unlock()
handlers := p.handlers[*topic]
if handlers == nil {
handlers = make(map[*handler]bool)
2018-06-20 12:06:27 +00:00
p.handlers[*topic] = handlers
log.Debug("registered handler", "capabilities", hndlr.caps)
}
if hndlr.caps == nil {
hndlr.caps = &handlerCaps{}
}
handlers[hndlr] = true
capabilities, ok := p.getTopicHandlerCaps(*topic)
if !ok {
capabilities = &handlerCaps{}
p.setTopicHandlerCaps(*topic, capabilities)
2018-06-20 12:06:27 +00:00
}
if hndlr.caps.raw {
capabilities.raw = true
}
if hndlr.caps.prox {
capabilities.prox = true
}
return func() { p.deregister(topic, hndlr) }
2018-06-20 12:06:27 +00:00
}
func (p *Pss) deregister(topic *Topic, hndlr *handler) {
2018-06-20 12:06:27 +00:00
p.handlersMu.Lock()
defer p.handlersMu.Unlock()
handlers := p.handlers[*topic]
if len(handlers) > 1 {
2018-06-20 12:06:27 +00:00
delete(p.handlers, *topic)
// topic caps might have changed now that a handler is gone
caps := &handlerCaps{}
for h := range handlers {
if h.caps.raw {
caps.raw = true
}
if h.caps.prox {
caps.prox = true
}
}
p.setTopicHandlerCaps(*topic, caps)
2018-06-20 12:06:27 +00:00
return
}
delete(handlers, hndlr)
2018-06-20 12:06:27 +00:00
}
// Filters incoming messages for processing or forwarding.
// Check if address partially matches
// If yes, it CAN be for us, and we process it
// Only passes error to pss protocol handler if payload is not valid pssmsg
func (p *Pss) handlePssMsg(ctx context.Context, msg interface{}) error {
2018-06-20 12:06:27 +00:00
metrics.GetOrRegisterCounter("pss.handlepssmsg", nil).Inc(1)
pssmsg, ok := msg.(*PssMsg)
if !ok {
return fmt.Errorf("invalid message type. Expected *PssMsg, got %T ", msg)
}
log.Trace("handler", "self", label(p.Kademlia.BaseAddr()), "topic", label(pssmsg.Payload.Topic[:]))
2018-06-20 12:06:27 +00:00
if int64(pssmsg.Expire) < time.Now().Unix() {
metrics.GetOrRegisterCounter("pss.expire", nil).Inc(1)
log.Warn("pss filtered expired message", "from", common.ToHex(p.Kademlia.BaseAddr()), "to", common.ToHex(pssmsg.To))
2018-06-20 12:06:27 +00:00
return nil
}
if p.checkFwdCache(pssmsg) {
log.Trace("pss relay block-cache match (process)", "from", common.ToHex(p.Kademlia.BaseAddr()), "to", (common.ToHex(pssmsg.To)))
2018-06-20 12:06:27 +00:00
return nil
}
p.addFwdCache(pssmsg)
psstopic := Topic(pssmsg.Payload.Topic)
// raw is simplest handler contingency to check, so check that first
var isRaw bool
if pssmsg.isRaw() {
if capabilities, ok := p.getTopicHandlerCaps(psstopic); ok {
if !capabilities.raw {
log.Debug("No handler for raw message", "topic", psstopic)
return nil
}
}
isRaw = true
}
// check if we can be recipient:
// - no prox handler on message and partial address matches
// - prox handler on message and we are in prox regardless of partial address match
// store this result so we don't calculate again on every handler
var isProx bool
if capabilities, ok := p.getTopicHandlerCaps(psstopic); ok {
isProx = capabilities.prox
}
isRecipient := p.isSelfPossibleRecipient(pssmsg, isProx)
if !isRecipient {
log.Trace("pss msg forwarding ===>", "pss", common.ToHex(p.BaseAddr()), "prox", isProx)
2018-06-20 12:06:27 +00:00
return p.enqueue(pssmsg)
}
log.Trace("pss msg processing <===", "pss", common.ToHex(p.BaseAddr()), "prox", isProx, "raw", isRaw, "topic", label(pssmsg.Payload.Topic[:]))
if err := p.process(pssmsg, isRaw, isProx); err != nil {
2018-06-20 12:06:27 +00:00
qerr := p.enqueue(pssmsg)
if qerr != nil {
return fmt.Errorf("process fail: processerr %v, queueerr: %v", err, qerr)
}
}
return nil
}
// Entry point to processing a message for which the current node can be the intended recipient.
// Attempts symmetric and asymmetric decryption with stored keys.
// Dispatches message to all handlers matching the message topic
func (p *Pss) process(pssmsg *PssMsg, raw bool, prox bool) error {
2018-06-20 12:06:27 +00:00
metrics.GetOrRegisterCounter("pss.process", nil).Inc(1)
var err error
var recvmsg *whisper.ReceivedMessage
var payload []byte
var from PssAddress
2018-06-20 12:06:27 +00:00
var asymmetric bool
var keyid string
var keyFunc func(envelope *whisper.Envelope) (*whisper.ReceivedMessage, string, PssAddress, error)
2018-06-20 12:06:27 +00:00
envelope := pssmsg.Payload
psstopic := Topic(envelope.Topic)
if raw {
2018-06-20 12:06:27 +00:00
payload = pssmsg.Payload.Data
} else {
if pssmsg.isSym() {
keyFunc = p.processSym
} else {
asymmetric = true
keyFunc = p.processAsym
}
recvmsg, keyid, from, err = keyFunc(envelope)
if err != nil {
return errors.New("Decryption failed")
}
payload = recvmsg.Payload
}
if len(pssmsg.To) < addressLength || prox {
err = p.enqueue(pssmsg)
2018-06-20 12:06:27 +00:00
}
p.executeHandlers(psstopic, payload, from, raw, prox, asymmetric, keyid)
return err
}
2018-06-20 12:06:27 +00:00
// copy all registered handlers for respective topic in order to avoid data race or deadlock
func (p *Pss) getHandlers(topic Topic) (ret []*handler) {
p.handlersMu.RLock()
defer p.handlersMu.RUnlock()
for k := range p.handlers[topic] {
ret = append(ret, k)
}
return ret
2018-06-20 12:06:27 +00:00
}
func (p *Pss) executeHandlers(topic Topic, payload []byte, from PssAddress, raw bool, prox bool, asymmetric bool, keyid string) {
2018-06-20 12:06:27 +00:00
handlers := p.getHandlers(topic)
all: new p2p node representation (#17643) Package p2p/enode provides a generalized representation of p2p nodes which can contain arbitrary information in key/value pairs. It is also the new home for the node database. The "v4" identity scheme is also moved here from p2p/enr to remove the dependency on Ethereum crypto from that package. Record signature handling is changed significantly. The identity scheme registry is removed and acceptable schemes must be passed to any method that needs identity. This means records must now be validated explicitly after decoding. The enode API is designed to make signature handling easy and safe: most APIs around the codebase work with enode.Node, which is a wrapper around a valid record. Going from enr.Record to enode.Node requires a valid signature. * p2p/discover: port to p2p/enode This ports the discovery code to the new node representation in p2p/enode. The wire protocol is unchanged, this can be considered a refactoring change. The Kademlia table can now deal with nodes using an arbitrary identity scheme. This requires a few incompatible API changes: - Table.Lookup is not available anymore. It used to take a public key as argument because v4 protocol requires one. Its replacement is LookupRandom. - Table.Resolve takes *enode.Node instead of NodeID. This is also for v4 protocol compatibility because nodes cannot be looked up by ID alone. - Types Node and NodeID are gone. Further commits in the series will be fixes all over the the codebase to deal with those removals. * p2p: port to p2p/enode and discovery changes This adapts package p2p to the changes in p2p/discover. All uses of discover.Node and discover.NodeID are replaced by their equivalents from p2p/enode. New API is added to retrieve the enode.Node instance of a peer. The behavior of Server.Self with discovery disabled is improved. It now tries much harder to report a working IP address, falling back to 127.0.0.1 if no suitable address can be determined through other means. These changes were needed for tests of other packages later in the series. * p2p/simulations, p2p/testing: port to p2p/enode No surprises here, mostly replacements of discover.Node, discover.NodeID with their new equivalents. The 'interesting' API changes are: - testing.ProtocolSession tracks complete nodes, not just their IDs. - adapters.NodeConfig has a new method to create a complete node. These changes were needed to make swarm tests work. Note that the NodeID change makes the code incompatible with old simulation snapshots. * whisper/whisperv5, whisper/whisperv6: port to p2p/enode This port was easy because whisper uses []byte for node IDs and URL strings in the API. * eth: port to p2p/enode Again, easy to port because eth uses strings for node IDs and doesn't care about node information in any way. * les: port to p2p/enode Apart from replacing discover.NodeID with enode.ID, most changes are in the server pool code. It now deals with complete nodes instead of (Pubkey, IP, Port) triples. The database format is unchanged for now, but we should probably change it to use the node database later. * node: port to p2p/enode This change simply replaces discover.Node and discover.NodeID with their new equivalents. * swarm/network: port to p2p/enode Swarm has its own node address representation, BzzAddr, containing both an overlay address (the hash of a secp256k1 public key) and an underlay address (enode:// URL). There are no changes to the BzzAddr format in this commit, but certain operations such as creating a BzzAddr from a node ID are now impossible because node IDs aren't public keys anymore. Most swarm-related changes in the series remove uses of NewAddrFromNodeID, replacing it with NewAddr which takes a complete node as argument. ToOverlayAddr is removed because we can just use the node ID directly.
2018-09-24 22:59:00 +00:00
peer := p2p.NewPeer(enode.ID{}, fmt.Sprintf("%x", from), []p2p.Cap{})
for _, h := range handlers {
if !h.caps.raw && raw {
log.Warn("norawhandler")
continue
}
if !h.caps.prox && prox {
log.Warn("noproxhandler")
continue
}
err := (h.f)(payload, peer, asymmetric, keyid)
2018-06-20 12:06:27 +00:00
if err != nil {
log.Warn("Pss handler failed", "err", err)
2018-06-20 12:06:27 +00:00
}
}
}
// will return false if using partial address
func (p *Pss) isSelfRecipient(msg *PssMsg) bool {
return bytes.Equal(msg.To, p.Kademlia.BaseAddr())
2018-06-20 12:06:27 +00:00
}
// test match of leftmost bytes in given message to node's Kademlia address
func (p *Pss) isSelfPossibleRecipient(msg *PssMsg, prox bool) bool {
local := p.Kademlia.BaseAddr()
// if a partial address matches we are possible recipient regardless of prox
// if not and prox is not set, we are surely not
if bytes.Equal(msg.To, local[:len(msg.To)]) {
return true
} else if !prox {
return false
}
depth := p.Kademlia.NeighbourhoodDepth()
swarm/network: Revised depth and health for Kademlia (#18354) * swarm/network: Revised depth calculation with tests * swarm/network: WIP remove redundant "full" function * swarm/network: WIP peerpot refactor * swarm/network: Make test methods submethod of peerpot and embed kad * swarm/network: Remove commented out code * swarm/network: Rename health test functions * swarm/network: Too many n's * swarm/network: Change hive Healthy func to accept addresses * swarm/network: Add Healthy proxy method for api in hive * swarm/network: Skip failing test out of scope for PR * swarm/network: Skip all tests dependent on SuggestPeers * swarm/network: Remove commented code and useless kad Pof member * swarm/network: Remove more unused code, add counter on depth test errors * swarm/network: WIP Create Healthy assertion tests * swarm/network: Roll back health related methods receiver change * swarm/network: Hardwire network minproxbinsize in swarm sim * swarm/network: Rework Health test to strict Pending add test for saturation And add test for as many as possible up to saturation * swarm/network: Skip discovery tests (dependent on SuggestPeer) * swarm/network: Remove useless minProxBinSize in stream * swarm/network: Remove unnecessary testing.T param to assert health * swarm/network: Implement t.Helper() in checkHealth * swarm/network: Rename check back to assert now that we have helper magic * swarm/network: Revert WaitTillHealthy change (deferred to nxt PR) * swarm/network: Kademlia tests GotNN => ConnectNN * swarm/network: Renames and comments * swarm/network: Add comments
2018-12-22 05:53:30 +00:00
po, _ := network.Pof(p.Kademlia.BaseAddr(), msg.To, 0)
log.Trace("selfpossible", "po", po, "depth", depth)
return depth <= po
2018-06-20 12:06:27 +00:00
}
/////////////////////////////////////////////////////////////////////
// SECTION: Message sending
/////////////////////////////////////////////////////////////////////
func (p *Pss) enqueue(msg *PssMsg) error {
select {
case p.outbox <- msg:
return nil
default:
}
metrics.GetOrRegisterCounter("pss.enqueue.outbox.full", nil).Inc(1)
return errors.New("outbox full")
}
// Send a raw message (any encryption is responsibility of calling client)
//
// Will fail if raw messages are disallowed
func (p *Pss) SendRaw(address PssAddress, topic Topic, msg []byte) error {
if err := validateAddress(address); err != nil {
return err
}
2018-06-20 12:06:27 +00:00
pssMsgParams := &msgParams{
raw: true,
}
payload := &whisper.Envelope{
Data: msg,
Topic: whisper.TopicType(topic),
}
pssMsg := newPssMsg(pssMsgParams)
pssMsg.To = address
pssMsg.Expire = uint32(time.Now().Add(p.msgTTL).Unix())
pssMsg.Payload = payload
p.addFwdCache(pssMsg)
err := p.enqueue(pssMsg)
if err != nil {
return err
}
// if we have a proxhandler on this topic
// also deliver message to ourselves
if capabilities, ok := p.getTopicHandlerCaps(topic); ok {
if p.isSelfPossibleRecipient(pssMsg, true) && capabilities.prox {
return p.process(pssMsg, true, true)
}
}
return nil
2018-06-20 12:06:27 +00:00
}
// Send a message using symmetric encryption
//
// Fails if the key id does not match any of the stored symmetric keys
func (p *Pss) SendSym(symkeyid string, topic Topic, msg []byte) error {
symkey, err := p.GetSymmetricKey(symkeyid)
if err != nil {
return fmt.Errorf("missing valid send symkey %s: %v", symkeyid, err)
}
psp, ok := p.getPeerSym(symkeyid, topic)
2018-06-20 12:06:27 +00:00
if !ok {
return fmt.Errorf("invalid topic '%s' for symkey '%s'", topic.String(), symkeyid)
}
return p.send(psp.address, topic, msg, false, symkey)
2018-06-20 12:06:27 +00:00
}
// Send a message using asymmetric encryption
//
// Fails if the key id does not match any in of the stored public keys
func (p *Pss) SendAsym(pubkeyid string, topic Topic, msg []byte) error {
if _, err := crypto.UnmarshalPubkey(common.FromHex(pubkeyid)); err != nil {
return fmt.Errorf("Cannot unmarshal pubkey: %x", pubkeyid)
}
psp, ok := p.getPeerPub(pubkeyid, topic)
2018-06-20 12:06:27 +00:00
if !ok {
return fmt.Errorf("invalid topic '%s' for pubkey '%s'", topic.String(), pubkeyid)
}
return p.send(psp.address, topic, msg, true, common.FromHex(pubkeyid))
2018-06-20 12:06:27 +00:00
}
// Send is payload agnostic, and will accept any byte slice as payload
// It generates an whisper envelope for the specified recipient and topic,
// and wraps the message payload in it.
// TODO: Implement proper message padding
func (p *Pss) send(to []byte, topic Topic, msg []byte, asymmetric bool, key []byte) error {
metrics.GetOrRegisterCounter("pss.send", nil).Inc(1)
if key == nil || bytes.Equal(key, []byte{}) {
return fmt.Errorf("Zero length key passed to pss send")
}
padding := make([]byte, p.paddingByteSize)
c, err := rand.Read(padding)
if err != nil {
return err
} else if c < p.paddingByteSize {
return fmt.Errorf("invalid padding length: %d", c)
}
wparams := &whisper.MessageParams{
TTL: defaultWhisperTTL,
Src: p.privateKey,
Topic: whisper.TopicType(topic),
WorkTime: defaultWhisperWorkTime,
PoW: defaultWhisperPoW,
Payload: msg,
Padding: padding,
}
if asymmetric {
pk, err := crypto.UnmarshalPubkey(key)
if err != nil {
return fmt.Errorf("Cannot unmarshal pubkey: %x", key)
}
wparams.Dst = pk
} else {
wparams.KeySym = key
}
// set up outgoing message container, which does encryption and envelope wrapping
woutmsg, err := whisper.NewSentMessage(wparams)
if err != nil {
return fmt.Errorf("failed to generate whisper message encapsulation: %v", err)
}
// performs encryption.
// Does NOT perform / performs negligible PoW due to very low difficulty setting
// after this the message is ready for sending
envelope, err := woutmsg.Wrap(wparams)
if err != nil {
return fmt.Errorf("failed to perform whisper encryption: %v", err)
}
log.Trace("pssmsg whisper done", "env", envelope, "wparams payload", common.ToHex(wparams.Payload), "to", common.ToHex(to), "asym", asymmetric, "key", common.ToHex(key))
// prepare for devp2p transport
pssMsgParams := &msgParams{
sym: !asymmetric,
}
pssMsg := newPssMsg(pssMsgParams)
pssMsg.To = to
pssMsg.Expire = uint32(time.Now().Add(p.msgTTL).Unix())
pssMsg.Payload = envelope
err = p.enqueue(pssMsg)
if err != nil {
return err
}
if capabilities, ok := p.getTopicHandlerCaps(topic); ok {
if p.isSelfPossibleRecipient(pssMsg, true) && capabilities.prox {
return p.process(pssMsg, true, true)
}
}
return nil
2018-06-20 12:06:27 +00:00
}
// sendFunc is a helper function that tries to send a message and returns true on success.
// It is set here for usage in production, and optionally overridden in tests.
var sendFunc = sendMsg
// tries to send a message, returns true if successful
func sendMsg(p *Pss, sp *network.Peer, msg *PssMsg) bool {
var isPssEnabled bool
info := sp.Info()
for _, capability := range info.Caps {
if capability == p.capstring {
isPssEnabled = true
break
}
}
if !isPssEnabled {
log.Error("peer doesn't have matching pss capabilities, skipping", "peer", info.Name, "caps", info.Caps)
return false
}
// get the protocol peer from the forwarding peer cache
p.fwdPoolMu.RLock()
pp := p.fwdPool[sp.Info().ID]
p.fwdPoolMu.RUnlock()
err := pp.Send(context.TODO(), msg)
if err != nil {
metrics.GetOrRegisterCounter("pss.pp.send.error", nil).Inc(1)
log.Error(err.Error())
}
return err == nil
}
// Forwards a pss message to the peer(s) based on recipient address according to the algorithm
// described below. The recipient address can be of any length, and the byte slice will be matched
// to the MSB slice of the peer address of the equivalent length.
//
// If the recipient address (or partial address) is within the neighbourhood depth of the forwarding
// node, then it will be forwarded to all the nearest neighbours of the forwarding node. In case of
// partial address, it should be forwarded to all the peers matching the partial address, if there
// are any; otherwise only to one peer, closest to the recipient address. In any case, if the message
// forwarding fails, the node should try to forward it to the next best peer, until the message is
// successfully forwarded to at least one peer.
2018-06-20 12:06:27 +00:00
func (p *Pss) forward(msg *PssMsg) error {
metrics.GetOrRegisterCounter("pss.forward", nil).Inc(1)
sent := 0 // number of successful sends
2018-06-20 12:06:27 +00:00
to := make([]byte, addressLength)
copy(to[:len(msg.To)], msg.To)
neighbourhoodDepth := p.Kademlia.NeighbourhoodDepth()
2018-06-20 12:06:27 +00:00
// luminosity is the opposite of darkness. the more bytes are removed from the address, the higher is darkness,
// but the luminosity is less. here luminosity equals the number of bits given in the destination address.
luminosityRadius := len(msg.To) * 8
2018-06-20 12:06:27 +00:00
// proximity order function matching up to neighbourhoodDepth bits (po <= neighbourhoodDepth)
pof := pot.DefaultPof(neighbourhoodDepth)
2018-06-20 12:06:27 +00:00
// soft threshold for msg broadcast
broadcastThreshold, _ := pof(to, p.BaseAddr(), 0)
if broadcastThreshold > luminosityRadius {
broadcastThreshold = luminosityRadius
}
var onlySendOnce bool // indicates if the message should only be sent to one peer with closest address
// if measured from the recipient address as opposed to the base address (see Kademlia.EachConn
// call below), then peers that fall in the same proximity bin as recipient address will appear
// [at least] one bit closer, but only if these additional bits are given in the recipient address.
if broadcastThreshold < luminosityRadius && broadcastThreshold < neighbourhoodDepth {
broadcastThreshold++
onlySendOnce = true
}
p.Kademlia.EachConn(to, addressLength*8, func(sp *network.Peer, po int) bool {
if po < broadcastThreshold && sent > 0 {
return false // stop iterating
2018-06-20 12:06:27 +00:00
}
if sendFunc(p, sp, msg) {
sent++
if onlySendOnce {
return false
}
if po == addressLength*8 {
// stop iterating if successfully sent to the exact recipient (perfect match of full address)
return false
}
2018-06-20 12:06:27 +00:00
}
return true
2018-06-20 12:06:27 +00:00
})
// if we failed to send to anyone, re-insert message in the send-queue
2018-06-20 12:06:27 +00:00
if sent == 0 {
log.Debug("unable to forward to any peers")
if err := p.enqueue(msg); err != nil {
metrics.GetOrRegisterCounter("pss.forward.enqueue.error", nil).Inc(1)
log.Error(err.Error())
return err
}
}
// cache the message
p.addFwdCache(msg)
return nil
}
/////////////////////////////////////////////////////////////////////
// SECTION: Caching
/////////////////////////////////////////////////////////////////////
// cleanFwdCache is used to periodically remove expired entries from the forward cache
func (p *Pss) cleanFwdCache() {
metrics.GetOrRegisterCounter("pss.cleanfwdcache", nil).Inc(1)
p.fwdCacheMu.Lock()
defer p.fwdCacheMu.Unlock()
for k, v := range p.fwdCache {
if v.expiresAt.Before(time.Now()) {
delete(p.fwdCache, k)
}
}
}
func label(b []byte) string {
return fmt.Sprintf("%04x", b[:2])
}
2018-06-20 12:06:27 +00:00
// add a message to the cache
func (p *Pss) addFwdCache(msg *PssMsg) error {
metrics.GetOrRegisterCounter("pss.addfwdcache", nil).Inc(1)
var entry pssCacheEntry
var ok bool
p.fwdCacheMu.Lock()
defer p.fwdCacheMu.Unlock()
digest := p.digest(msg)
if entry, ok = p.fwdCache[digest]; !ok {
entry = pssCacheEntry{}
}
entry.expiresAt = time.Now().Add(p.cacheTTL)
p.fwdCache[digest] = entry
return nil
}
// check if message is in the cache
func (p *Pss) checkFwdCache(msg *PssMsg) bool {
p.fwdCacheMu.Lock()
defer p.fwdCacheMu.Unlock()
digest := p.digest(msg)
entry, ok := p.fwdCache[digest]
if ok {
if entry.expiresAt.After(time.Now()) {
log.Trace("unexpired cache", "digest", fmt.Sprintf("%x", digest))
metrics.GetOrRegisterCounter("pss.checkfwdcache.unexpired", nil).Inc(1)
return true
}
metrics.GetOrRegisterCounter("pss.checkfwdcache.expired", nil).Inc(1)
}
return false
}
// Digest of message
func (p *Pss) digest(msg *PssMsg) pssDigest {
return p.digestBytes(msg.serialize())
}
func (p *Pss) digestBytes(msg []byte) pssDigest {
hasher := p.hashPool.Get().(hash.Hash)
2018-06-20 12:06:27 +00:00
defer p.hashPool.Put(hasher)
hasher.Reset()
hasher.Write(msg)
2018-06-20 12:06:27 +00:00
digest := pssDigest{}
key := hasher.Sum(nil)
copy(digest[:], key[:digestLength])
return digest
}
func validateAddress(addr PssAddress) error {
if len(addr) > addressLength {
return errors.New("address too long")
}
return nil
}